package cheme.ProcessModel.ModelActions;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.gef.Disposable;
import org.eclipse.gef.commands.CompoundCommand;

import cheme.ProcessModel.IModelElement;
import cheme.ProcessModel.Cmds.IChemeCommand;

public interface ICommandActionMap extends Disposable
{
	enum TestCompositionMode
	{
		ModelAndCommand, 		//Both elements must test successfully 
		ModelOrCommand, 		//only one need test successfully
		ModelExcludeCommand,	//only one must test successfully
		CommandExcludeModel		//only one must test successfully
	};
		
	CompoundCommand map(IChemeCommand originalCmd);
	
	/**
	 * Allow subclasses to perform type specific tests on the command or model element
	 * that originated the command.
	 * @param modelElement
	 * @return
	 */
	Boolean test(Object target);
	//Boolean test(IChemeCommand originalCmd);
	
	/**
	 * Allow subclasses to compose commands as needed.
	 * @param element
	 * @return
	 */
	CompoundCommand composeCommand(IModelElement element);
	
	void add(ICommandActionMap map);
	
	/**
	 * Disposes all contained maps.
	 */
	void dispose();

	/**
	 * @return the List of contained maps
	 */
	List<ICommandActionMap> getMaps();
	
	/**
	 * @return <code>true</code> if the map is empty
	 */
	boolean isEmpty();
	
	/**
	 * @return the number of contained maps
	 */
	int size();
	
	/**
	 * Returns the simplest form of this map that is equivalent.  
	 * This is useful for removing unnecessary nesting of maps.
	 * @return the simplest form of this map that is equivalent
	 */
	ICommandActionMap unwrap();

	/**
	 * @param type the type to set
	 */
	void setTargetModelElelementClass(Class targetModelElementClass);

	/**
	 * @return the type
	 */
	Class getTargetModelElelementClass();

	/**
	 * @param targetCommandCommand the targetCommandCommand to set
	 */
	void setTargetCommandClass(Class targetCommandClass);

	/**
	 * @return the targetCommandCommand
	 */
	Class getTargetCommandClass();

	/**
	 * @param testCompositionMode the testCompositionMode to set
	 */
	void setTestCompositionMode(TestCompositionMode testCompositionMode);

	/**
	 * @return the testCompositionMode
	 */
	public TestCompositionMode getTestCompositionMode();
	
	public abstract class CommandActionMapStub implements ICommandActionMap
	{
		public static final CommandActionMapStub EmptyMap = null;
		
		public static final CompoundCommand NullReturnValue = new CompoundCommand();
		
		public static final Class NoTargetModelElementClass = null;
		public static final Class NoTargetCommandClass = null;
		
		private List<ICommandActionMap> subMapList = new ArrayList<ICommandActionMap>();
		
		private Class targetModelElementClass;
		private Class targetCommandClass;
		
		private TestCompositionMode testCompositionMode;
		
		/*public CommandActionMapStub(Class target, TestCompositionMode testCompositionMode)
		{
			CommonInit(targetModelElementClass, NoTargetCommandClass, TestCompositionMode.ModelExcludeCommand);
		}
				
		public CommandActionMapStub(Class targetModelElementClass, Class targetCommandClass, TestCompositionMode testCompositionMode)
		{
			CommonInit(targetModelElementClass, targetCommandClass, testCompositionMode);
		}
		
		protected void CommonInit(Class targetModelElementClass, Class targetCommandClass)
		{
			CommonInit(targetModelElementClass, targetCommandClass, TestCompositionMode.ModelOrCommand);
		}
		
		protected void CommonInit(Class targetModelElementClass, Class targetCommandClass, TestCompositionMode testCompositionMode)
		{
			setTargetModelElelementClass(targetModelElementClass);
			setTargetCommandClass(targetCommandClass);
			setTestCompositionMode(testCompositionMode);
			
			testCompositionMode = TestCompositionMode.ModelOrCommand;
		}*/
		
		public CompoundCommand map(IChemeCommand originalCmd)
		{
			if(null == originalCmd)
			{
				return NullReturnValue;
			}
			
			CompoundCommand returnValue = NullReturnValue;
			
			IModelElement sourceModelElement = (IModelElement)originalCmd.getSource();
			Class sourceModelElementClass = sourceModelElement.getClass();
			
			Class sourceCommandClass = originalCmd.getClass();
			
			//Does the source command type match the target type?
			Boolean commandTestResult = 
				(NoTargetCommandClass != getTargetCommandClass() && 
						getTargetCommandClass() == sourceCommandClass);
			
			//Does the source element type match the target type?
			Boolean modelElementTestResult = 
				(NoTargetModelElementClass != getTargetModelElelementClass() && 
				 getTargetModelElelementClass() == sourceModelElementClass);
			
			TestCompositionMode localTestCompositionMode = getTestCompositionMode();
			
			Boolean initialTestResult = false;
					
			//first perform the local test of type matching
			switch(localTestCompositionMode)
			{
				case ModelAndCommand:
					initialTestResult = (modelElementTestResult && commandTestResult);
					break;
				case ModelOrCommand:
					initialTestResult = (modelElementTestResult || commandTestResult);
					break;
				case ModelExcludeCommand:
					initialTestResult = (modelElementTestResult && !commandTestResult);
					break;
				case CommandExcludeModel:
					initialTestResult = (!modelElementTestResult && commandTestResult);
					break;
				default:
					//throw new Exception("Illegal TestCompositionMode option in CommandActionSetMapper.map(...)");
			}	
			
			if(false == initialTestResult)
			{
				//Failed the first test, do not pass go, do not collect $200.00.
				return NullReturnValue;
			}
			
			/*
			 * ASSUMPTION: commands are only created and composed at the end of a
			 * series of nested mappers, so only a single command is ever added.
			 * This means that once
			 */

			//passed the first test, compose a command if specified
			returnValue = composeCommand(sourceModelElement);
			
			if(NullReturnValue != returnValue)
			{
				return returnValue;
			}
			
			//if the local test passes, and a command has not been generated,
			//pass the mapping test on to any nested mappers by initiating any nested tests.
			//Note that these are mutually exclusive tests, so only one will pass.
			
			return submap(originalCmd);
		}
		
		private CompoundCommand submap(IChemeCommand originalCmd)
		{
			CompoundCommand returnValue = NullReturnValue;
			
			for (int i = 0; i < size(); i++)
			{
				returnValue = getMaps().get(i).map(originalCmd);
			}
			
			return returnValue;
		}
		
		/*
		 * If this mapper is specified with a composition command 
		 */
		//protected abstract CompoundCommand composeCommand(IModelElement element);
		
		public void add(ICommandActionMap map)
		{
			if (map != null)
			{
				subMapList.add(map);
			}
		}
		
		public void dispose()
		{
			for (int i = 0; i < size(); i++)
			{
				getMaps().get(i).dispose();
			}
		}

		public List<ICommandActionMap> getMaps()
		{
			return subMapList;
		}
		
		public boolean isEmpty()
		{
			return getMaps().isEmpty();
		}
		
		public int size()
		{
			return getMaps().size();
		}
		
		public ICommandActionMap unwrap()
		{
			switch (size())
			{
				case 0 :
					return EmptyMap;
				case 1 :
					return getMaps().get(0);
				default :
					return this;
			}
		}

		public void setTargetModelElelementClass(Class targetModelElementClass)
		{
			this.targetModelElementClass = targetModelElementClass;
		}

		public Class getTargetModelElelementClass()
		{
			return targetModelElementClass;
		}

		public void setTargetCommandClass(Class targetCommandClass)
		{
			this.targetCommandClass = targetCommandClass;
		}

		public Class getTargetCommandClass()
		{
			return targetCommandClass;
		}

		public void setTestCompositionMode(TestCompositionMode testCompositionMode)
		{
			this.testCompositionMode = testCompositionMode;
		}

		public TestCompositionMode getTestCompositionMode()
		{
			return testCompositionMode;
		}
	}
}
